package pac;

import io.OutputWriter;
import java.util.BitSet;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import datastructures.WorkUnit;

public class WorkHorse {
	
	private WorkUnit regionToSearch;
	private final SearchDatastructure searcher;
	private OutputWriter outputter;
	
	//See http://www.ibm.com/developerworks/java/library/j-jtp07233/index.html#N10244
	private Map<Double, Boolean> stage1Candidates = new ConcurrentHashMap<Double, Boolean> (10000, (float) 0.95);
	private Map<Double, Boolean> stage2AlreadyTested = new ConcurrentHashMap<Double, Boolean> ();
	
	private final double maxHashableCandidates; 
	private double numberOfTrials;
	private int totalNumberOfCandidates;
	private KeyGen keygen;
	
	int order;
	private final double caseOnlyCutoff;
	private final double caseControlCutoff;
	
	
	public WorkHorse ( WorkUnit searchRegion, SearchDatastructure I , OutputWriter O ) 
	{
		regionToSearch = searchRegion;
		searcher = I;
		outputter = O;
		maxHashableCandidates = searcher.userParams.getMaxHeapPerThread()*10000;
		keygen = new KeyGen(2*searcher.snpList.length);
		totalNumberOfCandidates = 0;
		
		order = 2;
		caseOnlyCutoff = searcher.significance.getCaseOnlyThreshold (order);
		caseControlCutoff = searcher.significance.getCaseControlThreshold(order);
	}
	
	
	public int requiredSamples() {
		return regionToSearch.getTotalRequiredTrials() ;
	}
		
	public int candidatesTested() {
		return totalNumberOfCandidates;
	}
	
	/*
	 * Do the sampling.
	 * Write to file if asked.
	 */
	public boolean doSampling( ) {
				
		System.out.println("Sampling " + regionToSearch.getTotalRequiredTrials() + " times ... ");
		
		//2. Get the run parameters
		int[] windowsToSearch = regionToSearch.getCoordinates();	
		int[] trial_spectrum = regionToSearch.getRequiredTrials();
		double[] power_spectrum = regionToSearch.getPowerDistribution();
		
		int Cases = searcher.userParams.getNumber_of_cases();
		int lengthOfBitSet1 = 2*searcher.windowMaker.counter[windowsToSearch[0]];
		int lengthOfBitSet2 = 2*searcher.windowMaker.counter[windowsToSearch[1]];
		
		BitSet result1 = new BitSet ( lengthOfBitSet1 );
		BitSet result2 = new BitSet ( lengthOfBitSet2 );
		
		int[][] snpIndices = new int [2][];
		Random randomizer = new Random( (long) Math.floor ( searcher.userParams.getRandomSeed()) );
		
		//Go over each k.
		for ( int k=0; k<trial_spectrum.length; k++ ) 
		{			
			//if this k has nothing to do, proceed.
			if( trial_spectrum[k] < 1 )
				continue;
			else
				System.out.println(trial_spectrum[k] + " samples @ k=" + k + ". Power=" + power_spectrum[k]);
			
			//Go over this for each k
			int max_trials_for_this_k = trial_spectrum[k];
			for(numberOfTrials=0;  
					numberOfTrials < max_trials_for_this_k && stage1Candidates.size() < maxHashableCandidates; 
						numberOfTrials++, trial_spectrum[k]--) 
			{
				//4.1 : Start with vector of all 1s
				result1.set (0, lengthOfBitSet1+1 ); //set to all ones.
				result2.set (0, lengthOfBitSet2+1 ); //set to all ones.
				
				//4.2 : Pick random individuals, do the AND
				for(int index=0; index<k; index++) {
					int pick = (int) Math.floor ( randomizer.nextDouble() * Cases );
					result1.and ( searcher.windowMaker.caseGenotypes[windowsToSearch[0]][ pick ] );
					result2.and ( searcher.windowMaker.caseGenotypes[windowsToSearch[1]][ pick ] );
				}

				//4.3 : mask out SNPs that are in relevant 
				int window1entries = result1.cardinality();
				int window2entries = result2.cardinality();
				if ( window1entries == 0  || window2entries ==0 ) // nothing to go on with
					continue;


				//4.4 pull out relevant bits, map them to global variable positions
				snpIndices[ 0 ] = new int[ window1entries ];
				snpIndices[ 1 ] = new int[ window2entries ];

				int bitNo = -1;
				for(int entry=0; entry<window1entries; entry++) {
					bitNo = result1.nextSetBit (bitNo +1);
					int snpNo = searcher.windowMaker.globalPositionHash[windowsToSearch[0]].get(bitNo);
					snpIndices[0][entry] = snpNo;
				}

				bitNo = -1;
				for(int entry=0; entry<window2entries; entry++) {
					bitNo = result2.nextSetBit (bitNo +1);
					int snpNo = searcher.windowMaker.globalPositionHash[windowsToSearch[1]].get(bitNo);
					snpIndices[1][entry] = snpNo;
				}

				//4.5 enumerate all combinations, hash these candidates
				for(int entry0=0; entry0 < snpIndices[0].length; entry0++) {
					int variable0 =snpIndices[0][entry0];
					double block0 = searcher.snpList[(int) Math.floor(variable0/2)].getBlockNo();

					for(int entry1=0 ; entry1 < snpIndices[1].length; entry1++) {				
						int variable1 = snpIndices[1][entry1];
						double block1 = searcher.snpList[(int) Math.floor(variable1/2)].getBlockNo();

						if ( Math.abs( block1 - block0 ) >= 1 ) 
						{		
							int[] variableArray = (variable0 < variable1) ? new int[] {variable0, variable1} : new int[] {variable1, variable0};
							double key = keygen.makeKey(variableArray);
							stage1Candidates.put ( key, true );										
						}
					}
				}
				
			 // end for-loop of this k	
			}
	
			// reset the required trials -- is this unnecessary because of java array pointer?
			if ( stage1Candidates.size() >= maxHashableCandidates) {
				k--; //revisit this k
				doFollowUp();
			}
			
			//no need to update remaining trials. array is a pointer : was automatically decremented.
			//regionToSearch.setRequiredTrials(trial_spectrum);  
			
		} // end for-loop all k
		
		//have you finished your work, or do you want your hastable cleared?
		if( regionToSearch.getTotalRequiredTrials() == 0 ) {
			doFollowUp();
			return true;
		}
		else
			return false;

	} // end doSampling()
	
	
	
	public void doFollowUp() {
		
		if (stage1Candidates.size() == 0 )
			return;
		else
			totalNumberOfCandidates += stage1Candidates.size();
		
		System.out.println("Testing " + totalNumberOfCandidates + " candidates ... ");
		
		//2. enumerate the stage 1 hits
		for ( Double entry : stage1Candidates.keySet() ) {
			
			//2.1 : Get the variables
			int[] variableArray = keygen.breakKey(entry);
		
			//2.5 : establish expected and observed on number of case carriers
			int[][] noOfCaseCarriers = searcher.snpIndices.getObservedCaseCarriers(searcher.userParams, variableArray);
			double pvalueCaseOnly = searcher.significance.TestCasesOnly ( order, noOfCaseCarriers );
			if ( pvalueCaseOnly > caseOnlyCutoff )				
				continue;
			
			int[][] noOfControlCarriers = searcher.snpIndices.getObservedControlCarriers(searcher.userParams, variableArray);
			double pvalueCaseControl = searcher.significance.TestCasesVsControls ( order, noOfCaseCarriers, noOfControlCarriers );
			if ( pvalueCaseControl <= caseControlCutoff && stage2AlreadyTested.get(entry)==null )	
			{		
				outputter.WriteOutput( variableArray, noOfCaseCarriers, pvalueCaseOnly, noOfControlCarriers, pvalueCaseControl );
				stage2AlreadyTested.put(entry, true);
			}
				
		}	//end iterate over all hashtable entries
		
		stage1Candidates.clear();
		return;
	}
	
	
	
	
}